package org.koin.core.definition;

import org.koin.core.qualifier.Qualifier;
import org.koin.core.scope.ScopeDefinition;
import org.koin.ext.ClassUtil;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;

/**
 * Koin bean definition
 * main structure to make definition in Koin
 *
 * @author Arnaud Giuliani
 */
public class BeanDefinition<T> {

    private Qualifier scopeQualifier;
    private Class primaryType;
    private Qualifier qualifier;
    private Definition<T> definition;
    private Kind kind;
    private List<Class> secondaryTypes = new ArrayList<>();
    private Options options = new Options();
    private Properties properties = new Properties();

    private Callbacks<T> callbacks = new Callbacks<>();

    public static String indexKey(Class clazz, Qualifier qualifier) {

        String result = ClassUtil.getFullName(clazz);
        if (qualifier != null && qualifier.value != null) {
            result = ClassUtil.getFullName(clazz) + "::" + qualifier.value;
        }
        return result;
    }

    @Override
    public String toString() {

        String defKind = kind.toString();
        String defType = "'" + ClassUtil.getFullName(primaryType) + "'";
        String defName = "";
        if (qualifier != null) {
            defName = ",qualifier:" + qualifier;
        }
        String defScope = ",scope:" + scopeQualifier;
        if (scopeQualifier.equals(ScopeDefinition.ROOT_SCOPE_QUALIFIER)) {
            defScope = "";
        }
        String defOtherTypes = "";
        if (!secondaryTypes.isEmpty()) {
            StringBuilder typesAsString = new StringBuilder();
            for (Class claz : secondaryTypes) {
                typesAsString.append(ClassUtil.getFullName(claz));
                typesAsString.append(",");
            }
            defOtherTypes = ",binds:" + typesAsString.toString();
        }

        StringBuilder result = new StringBuilder();
        result.append("[");
        result.append(defKind);
        result.append(":");
        result.append(defType);
        result.append(defName);
        result.append(defScope);
        result.append(defOtherTypes);
        result.append("]");
        return result.toString();
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) {
            return true;
        }
        if (o == null || getClass() != o.getClass()) {
            return false;
        }
        BeanDefinition<?> that = (BeanDefinition<?>) o;

        if (!Objects.equals(primaryType, that.primaryType)) {
            return false;
        }
        if (qualifier != null && that.qualifier != null) {
            if (!that.qualifier.value.equals(qualifier.value)) {
                return false;
            }
        }
        if ((qualifier == null && that.qualifier != null)
                || (qualifier != null && that.qualifier == null)) {
            return false;
        }
        if (!Objects.equals(scopeQualifier, that.scopeQualifier)) {
            return false;
        }
        return true;
    }

    public boolean hasType(Class claz) {
        return Objects.equals(primaryType, claz) || secondaryTypes.contains(claz);
    }

    public boolean is(Class clazz, Qualifier qualifier, Qualifier scopeDefinition) {
        return hasType(clazz)
                && Objects.equals(this.qualifier, qualifier)
                && Objects.equals(this.secondaryTypes, secondaryTypes);
    }

    public boolean canBind(Class primary, Class secondary) {
        return primaryType == primary && secondaryTypes.contains(secondary);
    }

    @Override
    public int hashCode() {
        int result = 0;
        if (qualifier != null) {
            result = qualifier.value.hashCode();
        }
        result = 31 * result + primaryType.hashCode();
        result = 31 * result + scopeQualifier.value.hashCode();
        return result;
    }

    public Qualifier getScopeQualifier() {
        return scopeQualifier;
    }

    public BeanDefinition setScopeQualifier(Qualifier scopeQualifier) {
        this.scopeQualifier = scopeQualifier;
        return this;
    }

    public Class getPrimaryType() {
        return primaryType;
    }

    public BeanDefinition setPrimaryType(Class primaryType) {
        this.primaryType = primaryType;
        return this;
    }

    public Qualifier getQualifier() {
        return qualifier;
    }

    public BeanDefinition setQualifier(Qualifier qualifier) {
        this.qualifier = qualifier;
        return this;
    }

    public Definition<T> getDefinition() {
        return definition;
    }

    public BeanDefinition setDefinition(Definition<T> definition) {
        this.definition = definition;
        return this;
    }

    public Kind getKind() {
        return kind;
    }

    public BeanDefinition setKind(Kind kind) {
        this.kind = kind;
        return this;
    }

    public List<Class> getSecondaryTypes() {
        return secondaryTypes;
    }

    public BeanDefinition setSecondaryTypes(List<Class> secondaryTypes) {
        this.secondaryTypes = secondaryTypes;
        return this;
    }

    public Options getOptions() {
        return options;
    }

    public BeanDefinition setOptions(Options options) {
        this.options = options;
        return this;
    }

    public Properties getProperties() {
        return properties;
    }

    public BeanDefinition setProperties(Properties properties) {
        this.properties = properties;
        return this;
    }

    public Callbacks<T> getCallbacks() {
        return callbacks;
    }

    public void setCallbacks(Callbacks<T> callbacks) {
        this.callbacks = callbacks;
    }
}
